﻿using Microsoft.Extensions.Logging;
using Nest;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
using Wicture.DbRESTFul;
using Wicture.DbRESTFul.Rpc.Client;
using Wicture.EPC.Common;
using Wicture.QPZS.EPC.Models;
using Wicture.QPZS.EPC.Services;
using Wicture.QPZS.EPC.Services.LiYang;
using Wicture.QPZS.EPC.Services.UserAuth.Base;
using Wicture.QPZS.Parts;
using Wicture.QPZS.Parts.Models;
using Wicture.QPZS.Parts.Repository;

namespace Wicture.QPZS.EPC.Repository
{
    public class EpcRepository : RpcRepository
    {
        private static LogicalException VinNotFoundException = new LogicalException("暂时未找到数据，请持续关注。", 612);
        private static LogicalException VinNotVaildException = new LogicalException("请输入正确的VIN码。", 612);
        private static readonly string[] ImageFileExtensions = new[] { "png", "jpg", "jpeg", "gif", "bmp" };
        public static VinParser vinParser = new VinParser();
        private static readonly Dictionary<string, int> MaxHitCountMap = new Dictionary<string, int> { { "benz", 500 } };
        //private static readonly int PageSize = 50;

        public object SearchParts(JObject param)
        {
            var brandCode = param.Value<string>("brandCode");
            var keywords = param.Value<string>("keywords");
            var selectorId = param.Value<string>("selectorId");
            try
            {
                param.RemoveEmptyValues();
                int maxHitCout = 50;
                if (MaxHitCountMap.ContainsKey(brandCode))
                {
                    maxHitCout = MaxHitCountMap[brandCode];
                }
                var parts = Search(keywords, brandCode, selectorId.Split('_')[0], 0, maxHitCout);
                var partCodes = parts.Documents.Select(q => q.partCode);
                if (parts.Total >= maxHitCout)
                {
                    partCodes = partCodes.Take(maxHitCout);
                }
                param["partCodes"] = string.Join(",", partCodes);
                if (string.IsNullOrEmpty(param.Value<string>("partCodes")))
                {
                    return null;
                }
                var data = this.Call<JObject>(vinParser.ParseServiceName(brandCode), "Search_Parts", param)?.data;
                if (data == null)
                {
                    return null;
                }
                List<PartSearch> partSearches = PartRepository.GetHighLights(parts, keywords);
                data["hits"] = JArray.FromObject(partSearches);
                return data;
            }
            catch (LogicalException)
            {
                throw;
            }
            catch (Exception ex)
            {
                LoggerManager.Logger.LogError(ex, $"Failed To SearchParts({brandCode})");
                LoggerManager.Logger.LogWithEmail(ex, $"Failed To SearchParts({brandCode})", CurrentUser, this.HttpContext);
                throw new LogicalException(EpcConsts.NormalErrorMessage);
            }
        }

        public object QueryBrandInfo(string brandCode)
        {
            return Call<JObject>("Wicture.QPZS.Member", "GetBrand", new { brandCode });
        }

        public object QueryFitModels(JObject param)
        {
            var brandCode = param.Value<string>("brandCode");
            try
            {
                if (!BrandDictionary.ContainsKey(brandCode))
                {
                    return null;
                }
                var data = Call<JObject>(vinParser.ParseServiceName(brandCode), "Query_FitModels", param)?.data;
                return data;
            }
            catch (LogicalException)
            {
                throw;
            }
            catch (Exception ex)
            {
                LoggerManager.Logger.LogError(ex, $"Failed To QueryFitModels({brandCode})");
                LoggerManager.Logger.LogWithEmail(ex, $"Failed To QueryFitModels({brandCode})", CurrentUser, this.HttpContext);
                throw new LogicalException(EpcConsts.NormalErrorMessage);
            }
        }

        public object QueryParts(JObject param)
        {
            var brandCode = param.Value<string>("brandCode");
            try
            {
                UserAuthProcessor.PartListPreProcess(this, brandCode);
                var data = Call<JArray>(vinParser.ParseServiceName(brandCode), "Query_Parts", param)?.data;
                if(brandCode == "benz")
                {
                    BuildBenzColorPartsPrice(data);
                }
                UserAuthProcessor.HandlePartListResult(this, data, brandCode);
                return data;
            }
            catch (LogicalException)
            {
                throw;
            }
            catch (Exception ex)
            {
                LoggerManager.Logger.LogError(ex, $"Failed To QueryParts({brandCode})");
                LoggerManager.Logger.LogWithEmail(ex, $"Failed To QueryParts({brandCode})", CurrentUser, this.HttpContext);
                throw new LogicalException(EpcConsts.NormalErrorMessage);
            }
        }

        private void BuildBenzColorPartsPrice(JArray data)
        {
            List<string> colorPartCodes = new List<string>();
            foreach (var item in data)
            {
                string colorCode = item["colorCode"].ToString();
                if (!string.IsNullOrEmpty(colorCode))
                {
                    colorPartCodes.Add(item["partCode"].ToString());
                }
            }
            colorPartCodes = colorPartCodes.Where(x => !string.IsNullOrEmpty(x)).Distinct().ToList();
            if (colorPartCodes.Count > 0)
            {
                PartRepository pp = new PartRepository();
                var priceDic = pp.SearchByPartCodes(colorPartCodes.ToArray(), "benz").Documents.GroupBy(x => x.partCode).ToDictionary(k => k.Key, v => v.First());
                foreach (var item in data)
                {
                    string colorCode = item["colorCode"].ToString();
                    if (!string.IsNullOrEmpty(colorCode))
                    {
                        var partCode = item["partCode"].ToString();
                        if (priceDic.ContainsKey(partCode))
                        {
                            var price = priceDic[partCode];
                            item["price"] = price.price;
                            item["cost"] = price.cost;
                            item["priceUpdatedAt"] = price.priceUpdatedAt;
                        }
                    }
                }
            }
        }

        public object QueryPartDetail(JObject param)
        {
            var brandCode = param.Value<string>("brandCode");
            var partCode = param.Value<string>("partCode");
            JObject data = null;
            try
            {
                if (!BrandDictionary.ContainsKey(brandCode))
                {
                    data = QueryPartByPartCodeFromES(partCode, brandCode);
                }
                else
                {
                    data = Call<JObject>(vinParser.ParseServiceName(brandCode), "Query_Part_Detail", param)?.data;
                    if (data == null)
                    {
                        data = QueryPartByPartCodeFromES(partCode, brandCode);
                    }
                }
                UserAuthProcessor.HandlePartDetailResult(this, data, brandCode);
            }
            catch (LogicalException)
            {
                throw;
            }
            catch (Exception ex)
            {
                LoggerManager.Logger.LogError(ex, $"Failed To QueryPartDetail({brandCode})");
                LoggerManager.Logger.LogWithEmail(ex, $"Failed To QueryPartDetail({brandCode})", CurrentUser, this.HttpContext);
                throw new LogicalException(EpcConsts.NormalErrorMessage);
            }
            return data;
        }

        private JObject QueryPartByPartCodeFromES(string partCode, string brandCode)
        {
            JObject data = null;
            if (string.IsNullOrEmpty(partCode))
            {
                return null;
            }
            PartRepository pr = new PartRepository();
            var partInfo = pr.SearchByPartCodes(new string[] { partCode }, brandCode).Documents.FirstOrDefault();
            if (partInfo == null)
            {
                return null;
            }
            data = JObject.FromObject(partInfo);
            var replacementPartNo = partInfo.replacementPartNo;
            if (!string.IsNullOrEmpty(replacementPartNo))
            {
                var repinfos = pr.SearchByPartCodes(replacementPartNo.Split(','), brandCode).Documents;
                if (repinfos != null && repinfos.Count > 0)
                {
                    data["replacements"] = JArray.FromObject(repinfos);
                }
            }
            return data;
        }

        private ISearchResponse<Part> Search(string keyword, string brandCode, string catalogCode, int index = 0, int size = 999)
        {
            using (var settings = new ConnectionSettings(new Uri(ElasticSearchConfig.Config.ElasticSearchUrl)))
            {
                settings.BasicAuthentication(ElasticSearchConfig.Config.ElasticSearchUserName, ElasticSearchConfig.Config.ElasticSearchPassword)
                .DefaultIndex("accessory")
                .DefaultMappingFor<Part>(m => m.IndexName("accessory"));
                var client = new ElasticClient(settings);
                var query = new QueryContainer();

                keyword = EsHelper.FilterEsSpecWords(keyword);
                var stringQueryPartCode = new QueryStringQuery
                {
                    Fields = new string[] { "partCode" },
                    Query = $"*{keyword}*"
                };

                var brandCodeQuery = new TermQuery()
                {
                    Field = "brandCode",
                    Value = brandCode
                };
                var IsEpcQuery = new TermQuery()
                {
                    Field = "dataSource",
                    Value = 1
                };

                var catalogQuery = new QueryStringQuery()
                {
                    Fields = new string[] { "fitModels" },
                    Query = $"*,{catalogCode},*"
                };

                query = brandCodeQuery && IsEpcQuery && catalogQuery && stringQueryPartCode;

                var response = PartRepository.QueryPartsFromES(client, query, index, size);

                if (response.Hits.Count > 0)
                {
                    //tokens = new List<string>() { keyword };
                    return response;
                }

                query = new MatchQuery
                {
                    Field = "partName",
                    Query = keyword,
                    Fuzziness = Fuzziness.Auto,
                    Operator = Operator.And
                };

                query = brandCodeQuery && IsEpcQuery && catalogQuery && query;

                response = PartRepository.QueryPartsFromES(client, query, index, size);
                //tokens = client.Analyze(q => q.Analyzer("ik_max_word").Text(keyword)).Tokens.Select(q => q.Token).Distinct().ToList();
                if (response.Hits.Count > 0)
                {
                    return response;
                }

                query = new QueryStringQuery
                {
                    Fields = new string[] { "partName" },
                    Query = $"{keyword}"
                };
                query = brandCodeQuery && IsEpcQuery && catalogQuery && query;
                response = PartRepository.QueryPartsFromES(client, query, index, size);
                return response;
            }
        }

        private static readonly string[] _IgnoreVinCheckBrands = new string[] { "benz", "seat", "porsche", "mitsubishi", "nissan", "infiniti", "venucia", "landrover", "jaguar", "volvo", "vw", "audi", "skoda", "toyota", "lexus" };

        private void CheckVinValid(string vin)
        {
            if (vin == null)
            {
                throw VinNotVaildException;
            }
            if (vin.Length == 7 || vin.Length == 8)
            {
                return;
            }
            if (vin.Length != 17)
            {
                throw VinNotVaildException;
            }
            //白名单
            var (serviceName, brandCode) = VinServiceRule.MatchPrefixRule(vin);
            if (_IgnoreVinCheckBrands.Contains(brandCode))
            {
                return;
            }
            if (serviceName == null || brandCode == null)
            {
                (serviceName, brandCode) = VinServiceRule.MatchRegexRule(vin);
            }
            if (_IgnoreVinCheckBrands.Contains(brandCode))
            {
                return;
            }

            if (!VinHelper.CheckVinVaild(vin))
            {
                throw VinNotVaildException;
            }
        }

        public object QueryVin(JToken param)
        {
            string vin = param.Value<string>("vin");
            try
            {
                var vinInfo = QueryVinFree(param);
                var vinModelData = (vinInfo is JObject) ? (vinInfo as JObject) : JObject.FromObject(vinInfo);
                vin = vinModelData.Value<string>("vin");
                if (string.IsNullOrEmpty(vin))
                {
                    return vinInfo;
                }

                UserAuthProcessor.HandleVinQueryResult(this, vinModelData);
                return vinInfo;
            }
            catch (LogicalException ex)
            {
                LoggerManager.Logger.LogError(ex, $"Failed to query vin {vin}");

                if (ex.ErrorCode == "623")
                {
                    return new { resCode = 2 };
                }
                else if (ex.ErrorCode == "610")
                {
                    return new { resCode = 1 };
                }
                else
                {
                    return new { resCode = 3, resMessage = ex.Message };
                }
                
            }
            catch (Exception ex)
            {
                LoggerManager.Logger.LogError(ex, $"Failed to query vin {vin}");
                LoggerManager.Logger.LogWithEmail(ex, $"Failed to query vin {vin}", CurrentUser, this.HttpContext);
                return new { resCode = 3, resMessage = EpcConsts.NormalErrorMessage };
            }
        }

        public object QueryVinEasy(JToken param)
        {
            return QueryVinFree(param, false);
        }

        public object QueryVinFree(JToken param, bool needCheckVin = true)
        {
            var vin = param.Value<string>("vin");
            if (needCheckVin)
            {
                CheckVinValid(vin);
            }
            // 1. 先尝试通过ETKA规则处理
            object vinModel = vinParser.ParseVinForEtka(this, vin, needCheckVin);

            // 2. 如果没有处理结果，则通过一般规则处理
            if (vinModel == null)
            {
                vinModel = vinParser.ParseVinForRule(this, param, needCheckVin);
            }

            // 3. 如果还没有处理成功，则尝试通过OTS查询。
            if (vinModel == null)
            {
                string selectorId = param.Value<string>("selectorId");
                if (vinParser.IsLiYangVin(vin) && NeedQueryLiyang())
                {
                    //指定的vin格式调用力洋接口
                    vinModel = GetLiYangVinModel(vin, selectorId);
                }
                else
                {
                    // 否则直接通过vin服务获取数据。
                    string otsKey = vin;
                    if (!string.IsNullOrEmpty(selectorId))
                    {
                        otsKey += $"_{selectorId}";
                    }
                    vinModel = OTSVinService.QueryModelInfo(otsKey);

                    if (vinModel == null && vinParser.IsLiYangVin(vin))
                    {
                        //指定的vin格式调用力洋接口
                        vinModel = GetLiYangVinModel(vin, selectorId);
                    }
                }
            }
            if (vinModel == null)
            {
                return new { resCode = 1 };
            }
            return vinModel;
        }

        private Vin GetLiYangVinModel(string vin, string selectorId)
        {
            Vin vinModel = null;

            //调用力洋接口
            VinQueryResResult liyangModel = LiYangVinService.VinQuery(vin);

            if (liyangModel != null)
            {
                //调用v1接口
                string brandCode = GetBrandCodeByLiYangBrand(liyangModel.Brand);
                liyangModel.vin = vin;
                liyangModel.brandCode = brandCode;

                if (!string.IsNullOrEmpty(selectorId))
                {
                    liyangModel.selectorId = selectorId;
                }
                var vinObj = this.Call<JObject>(vinParser.ParseServiceName(brandCode), "Get_Model_ByLiYang", liyangModel)?.data;

                if (vinObj != null)
                {
                    vinModel = vinObj.ToObject<Vin>();
                }
                //存OTS
                if (NeedSaveOTS())
                {
                    string otsKey = vin;
                    if (!string.IsNullOrEmpty(selectorId))
                    {
                        otsKey += $"_{selectorId}";
                    }
                    OTSVinService.SaveOTS(otsKey, vinModel);
                }
            }
            return vinModel;
        }
        private bool NeedSaveOTS()
        {
            bool needSave;
            bool.TryParse(ConfigurationManager.Settings.Variables["LiYang.NeedSaveOTS"], out needSave);
            return needSave;
        }

        private bool NeedQueryLiyang()
        {
            bool needSave;
            bool.TryParse(ConfigurationManager.Settings.Variables["LiYang.NeedQuery"], out needSave);
            return needSave;
        }

        private string GetBrandCodeByLiYangBrand(string liyangBrand)
        {
            string brandCode = string.Empty;

            //别克|buick, 凯迪拉克|cadillac, 雪佛兰|chevrolet
            string liyangBrands = ConfigurationManager.Settings.Variables["LiYang.Brand"];
            if (!string.IsNullOrEmpty(liyangBrands))
            {
                string[] brands = liyangBrands.Split(new char[] { ',' });
                if (brands.Any())
                {
                    foreach (var item in brands)
                    {
                        //别克|buick
                        string[] relation = item.Split(new char[] { '|' });
                        if (relation.Any() && relation.Count() == 2)
                        {
                            if (liyangBrand.Equals(relation[0].Trim()))
                            {
                                brandCode = relation[1];
                                break;
                            }
                        }
                    }
                }
            }
            return brandCode;
        }

        public object RealTimeQueryPartPrice(JObject param)
        {
            var brandCode = param.Value<string>("brandCode");
            var partCode = param.Value<string>("partCode");
            if (!BrandDictionary.ContainsKey(brandCode))
            {
                return null;
            }
            var data = Call<JObject>(vinParser.ParseServiceName(brandCode), "Real_Time_Query_Part_Price", param)?.data;
            return data;
        }

        public object ClearOtsVin(JObject param)
        {
            string vinCode = param.Value<string>("vinCode");
            return OTSVinService.ClearVin(vinCode);
        }

        public object QuickQueryParts(JObject param)
        {
            var brandCode = param.Value<string>("brandCode");
            return Call<JArray>(vinParser.ParseServiceName(brandCode), "Quick_Query_Parts", param)?.data;
        }

        public object RealTimeQueryOEMStock(JObject param)
        {
            var brandCode = param.Value<string>("brandCode");

            string oemStockBrand = ConfigurationManager.Settings.Variables["BrandHasOEMStock"];
            if (string.IsNullOrEmpty(oemStockBrand) || !oemStockBrand.Split(new char[] { ',' }).Contains(brandCode, StringComparison.OrdinalIgnoreCase))
            {
                throw new LogicalException($"品牌{brandCode}不支持官方大库在线查询");
            }

            return Call<object>(vinParser.ParseServiceName(brandCode), "RealTime_Query_OEM_Stock", param)?.data;
        }
    }
}
